<!--
@license
Copyright 2017 GIVe Authors
*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

### Overview

`<bed-track-dom>` is the Web Component to display BED tracks. It's part of
`GIVe.BedTrack` object and is used to visualize data from the `GIVe.BedTrack`
object.

### Visibility level

### References
*   [`GIVe.TrackObject`](../index.html) for details on tracks in
general;
*   [`GIVe.BedTrack`](./bed-track/index.html) for details on BED
track implementation;
*   [Polymer element registration](https://www.polymer-project.org/1.0/docs/devguide/registering-elements)
for Polymer Element guide, including lifecycles, properties, methods and others.

-->
<dom-module id="bed-track-dom">
  <template>
  </template>
  <script>
var GIVe = (function (give) {
  'use strict'

  give.RasInfo = function (start, end, rasterCount, rasterFillThreshold) {
    this.prepareRaster(start, end, rasterCount)
    this.prepareInfo(0)
    this.RasterFillThreshold = rasterFillThreshold || 0.0001
  }

  give.RasInfo.prototype.prepareInfo = function (index, callback) {
    if (this._Raster.length > 1) {
      if (this._Info && typeof callback === 'function') {
        callback(this.getFlag(), this.getCurrIndex())
      }
      this._Info = this._Info ||
        new Array(this.constructor.RASTER_STATES.TotalNumOfStates)
      this._Info.fill(0)
      if (index < 0 || index >= this._Raster.length - 1) {
        // index out of bounds
        this._RasIndex = null
        throw new Error('Index out of bounds for raster! Raster length: ' +
          this._Raster.length + ', index: ' + index)
      }
      this._RasIndex = index || 0
    }
  }

  give.RasInfo.prototype.getFlag = function () {
    var maxFlag = this.constructor.RASTER_STATES.EMPTY
    this._Info.every(function (entry, index) {
      if (entry > parseInt((this.getRasterEnd() - this.getRasterStart()) *
        this.RasterFillThreshold)
      ) {
        maxFlag = index
        return true
      }
      return false
    }, this)
    return maxFlag
  }

  give.RasInfo.prototype.getRasterStart = function () {
    return this._Raster[this._RasIndex]
  }

  give.RasInfo.prototype.getRasterEnd = function () {
    return this._Raster[this._RasIndex + 1]
  }

  give.RasInfo.prototype.next = function (callback) {
    this.prepareInfo(this._RasIndex + 1, callback)
    return this._RasIndex
  }

  give.RasInfo.prototype.addData = function (length, flag) {
    for (let i = this.constructor.RASTER_STATES.EMPTY; i <= flag; i++) {
      this._Info[i] += length
    }
  }

  give.RasInfo.prototype.prepareRaster = function (start, end, rasterCount) {
    if (isNaN(rasterCount) || rasterCount < 1) {
      rasterCount = 1
    }
    if (end - start < rasterCount) {
      rasterCount = end - start
    }
    if (end - start > rasterCount) {
      let currCoor = start
      this._Raster = [currCoor]
      do {
        currCoor += parseInt((end - currCoor) / rasterCount + 0.5)
        rasterCount--
        this._Raster.push(currCoor)
      } while (currCoor < end)
    }
  }

  give.RasInfo.prototype.findRasIndex = function (coordinate) {
    // bring this._RasterizedInfo._RasterIndex up to date
    return give.locationOf(coordinate, this._Raster)
  }

  give.RasInfo.prototype.getCurrIndex = function () {
    return this._RasIndex
  }

  /**
   * genRasOutput - generate a series of consolidated non-empty rasterized
   *    region.
   *
   * @param  {Array<number>} RasFlags   description
   * @param  {number} startIndex description
   * @returns {Array<object>}  return consolidated non-empty objects.
   *    For example:
   *    '''
   *    [
   *      {
   *        'start': 123456,
   *        'end': 234567,
   *        'flag': 1 (=== give.RasInfo.RASTER_STATES.LINE)
   *      },
   *      ...
   *    ]
   *    '''
   */
  give.RasInfo.prototype.genRasOutput = function (RasFlags, startIndex) {
    var results = []
    var lastFlag
    var lastIndex = startIndex
    RasFlags.forEach(function (flag, index) {
      if (flag !== lastFlag) {
        if (lastFlag) {
          results.push({
            start: this._Raster[lastIndex],
            end: this._Raster[startIndex + index],
            flag: lastFlag
          })
        }
        lastIndex = startIndex + index
        lastFlag = flag
      }
    }, this)
    if (lastFlag) {
      results.push({
        start: this._Raster[lastIndex],
        end: this._Raster[startIndex + RasFlags.length],
        flag: lastFlag
      })
    }
    return results
  }

  give.RasInfo.RASTER_STATES = {
    TotalNumOfStates: 4,
    EMPTY: 0,
    LINE: 1,
    THIN: 2,
    THICK: 3
  }

  give.BedTrackDOM = Polymer({
    is: 'bed-track-dom',

    behaviors: [
      give.TrackDOMBehavior
    ],

    properties: {
      /**
       * whether this track honors itemRGB values provided by BED data.
       */
      honorItemRGB: {
        type: Boolean,
        value: false
      }
    },

    created: function () {
      /**
       * @property {number} GENE_MARGIN
       * margin size between genes when gene name is shown. Unit in px.
       */
      this.GENE_MARGIN = 10

      /**
       * @property {number} GENE_HEIGHT
       * height of gene entries. Proportion to `this.textSize`.
       */
      this.GENE_HEIGHT = 0.8

      /**
       * @property {number} GENE_NOTEXT_MARGIN
       * margin size between genes when no text is shown. Unit in px.
       */
      this.GENE_NOTEXT_MARGIN = 2

      /**
       * @property {number} ADAPTIVE_MAXLINES
       * limit to downgrade visibility
       */
      this.ADAPTIVE_MAXLINES = 12

      /**
       * @property {number} TRIANGLE_FILL
       * the fill color for the triangles (indicating clipped content)
       */
      this.TRIANGLE_FILL = 0xFFFFFF

      /**
       * @property {number} FORECOLOR_INDEX
       * the color index for fore color
       */
      this.FORECOLOR_INDEX = 0

      /**
       * @property {number} RASTER_SIZE
       * The size of raster in `DENSE` view in pixel.
       */
      this.RASTER_SIZE = 1
    },

    /**
     * ****** Implementation methods below ******
     */

    trackImpl: function (track, prop) {
      if (track.getSetting('honorItemRGB')) {
        this.honorItemRGB = track.getSetting('honorItemRGB', 'boolean')
      }

      this._RasInfo = null
    },

    drawData: function () {
      // this is to draw everything from this.data to the svg
      // Steps:
      //     put genes into lines (pack display)
      //    draw genes out line by line

      // clear text Margin svg
      this.clear()

      this.changeVisibility(this.parent.getSetting('visibility'), false)
      var numOfLines = 1
      var data = this._getDataObject(this.mainSvg.viewWindow.chr)

      this._RasInfo = null
      var megaGenes = null
      if (data) {
        while (!(numOfLines = this._prepareLines(data)) &&
          this.activeVisibility > give.TrackObject.StatusEnum.VIS_NONE);
        if (this.activeVisibility <=
          give.TrackObject.StatusEnum.VIS_NOTEXT
        ) {
          // Raster is needed
          this._RasInfo = new give.RasInfo(
            this.mainSvg.viewWindow.getStart(),
            this.mainSvg.viewWindow.getEnd(),
            this.windowWidth / this.RASTER_SIZE)
        }
        if (this.activeVisibility > give.TrackObject.StatusEnum.VIS_DENSE
        ) {
          data.traverse(
            this.mainSvg.viewWindow, this._drawSingleGene,
            this, null, false
          )
        } else {
          // create a series of "megagenes" with all overlapping genes
          // then draw the "megagenes" instead
          megaGenes = []
          data.traverse(this.mainSvg.viewWindow,
            this._aggregateTranscript.bind(this, megaGenes),
            this, null, false)
          megaGenes.forEach(this._drawSingleGene, this)
        }
      }

      // resize the heights
      this.updateLocationSize(null, null, null,
        this._calcHeight(numOfLines))
    },

    /**
     * ****** Private and helper methods below ******
     */

    /**
     * _calcHeight - calculate the height of this track
     *
     * @param  {number} [numOfLines] - number of lines in this track
     * @returns {number} height of this track
     */
    _calcHeight: function (numOfLines) {
      numOfLines = numOfLines || 1
      return (numOfLines * (this.fullHeightRatio + this.lineGapRatio) -
        this.lineGapRatio) * this.textSize
    },

    _aggregateTranscript: function (transcriptArray, transcript) {
      if (transcriptArray.length <= 0 ||
        !transcriptArray[transcriptArray.length - 1].merge(transcript)
      ) {
        transcriptArray.push(new give.AggregatedTranscript(transcript))
      }
    },

    /**
     * _allocateLineToTrans - test and allocate line number to a single
     *    transcript.
     *    Allocated line number will be written into `transcript` via its
     *    `lineY` property.
     *    Also, if the current visibility is at `DENSE`, fill the
     *    rasterized information.
     *
     * @param  {Array<object>} lineEnds - An array of objects depicting
     *    existing line ends for collision detection., object can have two
     *    properties:
     *    * end: the end coordinate of the line;
     *    * textAtLeft: whether this line has text at left (in text column).
     * @param  {TranscriptObject} transcript - the transcript to be
     *    allocated.
     * @returns {boolean} return `true` if allocation succeeds, `false` if
     *    allocation cannot be done under current settings.
     *    (This will happen when adaptive display is on and current active
     *    setting cannot hold all transcripts.)
     */
    _allocateLineToTrans: function (lineEnds, transcript) {
      // calculate x0 and x1 for the gene
      if (!this.regionInWindow(transcript)) {
        return true
      }
      if (this.activeVisibility <= give.TrackObject.StatusEnum.VIS_DENSE) {
        return true
      }
      var x0 = this.transformXCoordinate(transcript.getStartCoor(), true)
      var x1 = this.transformXCoordinate(transcript.getEndCoor(), true)
      var textAtLeft = false
      if (this.activeVisibility >
        give.TrackObject.StatusEnum.VIS_NOTEXT &&
        transcript.getGeneName(true)
      ) {
        var newLabel = this.drawText(x0 - this.TEXT_MARGIN_GAP,
          this.Y_HIDDEN, transcript.getGeneName(true), 'end')
        // move text to textMargin if out of bounds
        x0 = newLabel.getBBox().x
        if (this.textMargin && x0 < 0) {
          x0 = 0
          textAtLeft = true
        }
        this.removeElement(newLabel)
      }

      if (!lineEnds.some(function (lineEnd, index) {
        if (
          (lineEnd.end <= x0 && !(lineEnd.textAtLeft && textAtLeft)) ||
          this.activeVisibility <= give.TrackObject.StatusEnum.VIS_DENSE
        ) {
          // this gene can be fit into this line
          transcript.lineY = index
          lineEnd.end = x1 + (this.activeVisibility >
            give.TrackObject.StatusEnum.VIS_NOTEXT
            ? this.GENE_MARGIN : this.GENE_NOTEXT_MARGIN)
          return true
        }
        return false
      }, this)) {
        // no empty lines, create a new line
        if (this.parent.getSetting('adaptive', 'boolean') &&
          lineEnds.length >= this.ADAPTIVE_MAXLINES
        ) {
          // maximum number of lines exceeded
          // reduce visibility level by 1
          this.changeVisibility(-1, true)
          return false
        }
        transcript.lineY = lineEnds.length
        lineEnds.push({
          end: x1 + (this.activeVisibility >
            give.TrackObject.StatusEnum.VIS_NOTEXT
            ? this.GENE_MARGIN : this.GENE_NOTEXT_MARGIN),
          textAtLeft: textAtLeft
        })
      }
      return true
    },

    /**
     * _prepareLines - prepare lines for genes in adaptive cases.
     *    This is especially useful for display modes like `pack`, where
     *      items are stacked if space allows.
     *    When `this.parent.getSetting('adaptive', 'boolean') === true`, the
     *      actual display mode needs to be determined by the code. This
     *      function will calculate the x0 and x1 for every gene, taking
     *      text width into consideration, and increase display density
     *      accordingly.
     *    This function will allocate items to their corresponding lines by
     *      setting the `lineY` property.
     *
     * @param  {GiveTreeBase} data - data object used to prepare the lines.
     * @returns {number}  number of lines this track will take.
     */
    _prepareLines: function (data) {
      var lineEnds = [] // arrays of line end coordinates
      if (this.activeVisibility <= give.TrackObject.StatusEnum.VIS_DENSE
      ) {
        return 1
      }
      if (data.traverse(
        this.mainSvg.viewWindow,
        this._callTransFuncOnGene.bind(this,
          this._allocateLineToTrans.bind(this, lineEnds)
        ), this, null, true)
      ) {
        return lineEnds.length > 0 ? lineEnds.length : 1
      } else {
        return false
      }
    },

    /**
     * _drawOverflowTriangles - draw two overflow triangles if genes exceed
     *    the viewWindow
     *
     * @param  {TranscriptObject} transcript - the gene/transcript to be
     *    drawn. Only the entry provided here will be drawn, so if a gene
     *    is provided, only the gene, not its transcript(s) will be drawn.
     * @param  {number} colorRGB - RGB value of the color for the gene
     * @param  {number} height - full height for drawing (CDS for example).
     * @param  {number} yCoor - the y coordinate from the top to draw this
     *    gene/transcript. __Notice that genes drawn are vertically-aligned
     *    to the center so yCoor should be the coordinates at the center.__
     */
    _drawOverflowTriangles: function (transcript, colorRGB, height, yCoor) {
      var halfHeight = 0.5 * height
      if (this.transformXCoordinate(transcript.getStartCoor(), false) < 0) {
        // left triangles
        this.createRawPolygon([ '0,' + yCoor,
          halfHeight + ',' + (yCoor - halfHeight),
          halfHeight + ',' + yCoor,
          height + ',' + (yCoor - halfHeight),
          height + ',' + (yCoor + halfHeight),
          halfHeight + ',' + yCoor,
          halfHeight + ',' + (yCoor + halfHeight) ],
        { fill: this.TRIANGLE_FILL, stroke: colorRGB }
        )
      }

      if (this.transformXCoordinate(transcript.getEndCoor(), false) >
        this.windowWidth
      ) {
        // right triangles
        this.createRawPolygon([ this.windowWidth + ',' + yCoor,
          (this.windowWidth - halfHeight) + ',' + (yCoor - halfHeight),
          (this.windowWidth - halfHeight) + ',' + yCoor,
          (this.windowWidth - height) + ',' + (yCoor - halfHeight),
          (this.windowWidth - height) + ',' + (yCoor + halfHeight),
          (this.windowWidth - halfHeight) + ',' + yCoor,
          (this.windowWidth - halfHeight) + ',' + (yCoor + halfHeight) ],
        { fill: this.TRIANGLE_FILL, stroke: colorRGB }
        )
      }
    },

    /**
     * _callTransFuncOnGene - call functions on transcripts of genes, or
     *    the gene itself depends on visibility settings.
     *    If active visibility is greater than
     *    `give.TrackObject.StatusEnum.VIS_COLLAPSED`, then function will
     *    be called upon every transcript, otherwise it will be called upon
     *    the gene.
     *    Notice that the loop will be broken once `transcriptCallback`
     *    returns `false`.
     *
     * @param  {function} transcriptCallback - call back functions for
     *    each transcript (taking only the transcript as )
     * @param  {GeneObject} gene - the gene to be called upon.
     * @return {boolean} Whether the function call returns `true`
     */
    _callTransFuncOnGene: function (transcriptCallback, gene) {
      var transcripts
      if (this.activeVisibility >
        give.TrackObject.StatusEnum.VIS_COLLAPSED &&
        gene.transcripts
      ) {
        transcripts = gene.transcripts
      } else {
        transcripts = [gene]
      }
      return transcripts.every(transcriptCallback, this)
    },

    /**
     * _drawSingleTranscript - draw a single gene in the track SVG
     *
     * @param  {GeneObject} gene - the gene to be drawn.
     */
    _drawSingleGene: function (gene, index, arr) {
      this._callTransFuncOnGene(function (transcript) {
        if (this.regionInWindow(transcript)) {
          this._drawSingleTranscript(transcript,
            Array.isArray(arr) ? index === arr.length - 1 : false,
            null,
            (this.honorItemRGB && gene.itemRGB !== undefined)
              ? gene.itemRGB : null)
        }
        return true
      }, gene)
    },

    /**
     * _drawSingleTranscript - draw a single transcript in the track SVG
     *
     * @param  {TranscriptObject} transcript - the transcript to be
     *    drawn. Only the entry provided here will be drawn, so if a gene
     *    is provided, only the gene, not its transcript(s) will be drawn.
     * @param  {boolean} [lastTrans] - whether this transcript is the last one
     *    to draw. Used to finish rasterized drawing.
     * @param  {number} [yCoor] - the y coordinate from the top to draw this
     *    transcript. __Notice that genes drawn are vertically-aligned
     *    to the center so yCoor should be the coordinates at the center.__
     * @param  {number} [colorRGB] - RGB value of the color for the gene
     * @param  {number} [height] - full height for drawing (CDS for
     *    example).
     * @param  {number} [halfHeightRatio] - Ratio for lower height values in
     *    some regions (for example, UTRs). __This should be normalized by
     *    `height`.__
     * @param  {number} [lineHeight] - unit for all height values.
     */
    _drawSingleTranscript: function (
      transcript, lastTrans, yCoor, colorRGB, height, halfHeightRatio,
      lineHeight
    ) {
      height = height ||
        this.fullHeightRatio * this.textSize * this.GENE_HEIGHT
      halfHeightRatio = halfHeightRatio || this.halfHeightRatio
      lineHeight = lineHeight ||
        (this.fullHeightRatio + this.lineGapRatio) * this.textSize
      if (typeof (colorRGB) !== 'number') {
        colorRGB = this.colorSet[this.FORECOLOR_INDEX]
      }

      if (typeof (yCoor) !== 'number' || isNaN(yCoor)) {
        yCoor = (parseInt(
          this.activeVisibility > give.TrackObject.StatusEnum.VIS_DENSE
            ? transcript.lineY : 0) + 0.5) * lineHeight ||
          lineHeight / 2
      }

      if (this.activeVisibility > give.TrackObject.StatusEnum.VIS_NOTEXT) {
        this._drawSingleTranscriptBodyFullRes(transcript, yCoor, colorRGB,
          height, halfHeightRatio, lineHeight)
        // draw text
        var newLabel = this.drawText(
          this.transformXCoordinate(transcript.getStartCoor(), true) -
            this.TEXT_MARGIN_GAP,
          yCoor, transcript.getGeneName(true), 'end',
          {style: 'fill: ' + this.rgbToHex(colorRGB)}
        )

        // move text to textMargin if out of bounds
        if (this.textMargin && newLabel.getBBox().x < 0) {
          newLabel.setAttributeNS(null, 'x', this.textMargin)
          this.addElement(newLabel, this.textSvg)
        }
      } else {
        this._drawSingleTranscriptBodyRaster(transcript, lastTrans, yCoor,
          colorRGB, height, halfHeightRatio, lineHeight)
      }

      this._drawOverflowTriangles(transcript, colorRGB, height, yCoor)
    },

    /**
     * _drawSingleTranscriptBodyFullRes - draw a single transcript in the
     *    track SVG at full resolution
     *
     * @param  {TranscriptObject} transcript - the transcript to be
     *    drawn. Only the entry provided here will be drawn, so if a gene
     *    is provided, only the gene, not its transcript(s) will be drawn.
     * @param  {number} yCoor - the y coordinate from the top to draw this
     *    transcript. __Notice that genes drawn are vertically-aligned
     *    to the center so yCoor should be the coordinates at the center.__
     * @param  {number} colorRGB - RGB value of the color for the gene
     * @param  {number} height - full height for drawing (CDS for
     *    example).
     * @param  {number} halfHeightRatio - Ratio for lower height values in
     *    some regions (for example, UTRs). __This should be normalized by
     *    `height`.__
     * @param  {number} lineHeight - unit for all height values.
     */
    _drawSingleTranscriptBodyFullRes: function (
      transcript, yCoor, colorRGB, height, halfHeightRatio, lineHeight
    ) {
      /**
       * _processSingleBlock - helper function to draw only one block
       *
       * @param  {number} blockStart - the start coordinate of the block.
       * @param  {number} blockEnd - the end coordinate of the block.
       * @param  {number} [thickStart] - the start coordinate of thick
       *    portions, which may overlap with the block.
       * @param  {number} [thickEnd] - the end coordinate of thick
       *    portions, which may overlap with the block.
       * @param  {ChromRegionLiteral} [coor] - chromosomal region object.
       *    Used to cut back GC.
       */
      var _processSingleBlock = function (
        blockStart, blockEnd, thickStart, thickEnd, coor
      ) {
        coor = coor || new give.ChromRegion({chr: transcript.chr,
          start: blockStart,
          end: blockEnd,
          strand: transcript.strand})
        var isThick = false
        if (typeof thickStart === 'number' ||
          typeof thickEnd === 'number'
        ) {
          if (thickStart < blockEnd && thickStart > blockStart) {
            // CDS start is in this block
            coor.setStart(blockStart, true)
            coor.setEnd(thickStart)
            this.drawRectangle(coor, colorRGB,
              yCoor, height * halfHeightRatio,
              this.mainSvg, colorRGB,
              give.TrackDOMBehaviorImpl.VALIGN_CENTER
            )
            blockStart = thickStart
          }
          if (thickEnd < blockEnd && thickEnd > blockStart) {
            // CDS end is in this block
            coor.setStart(blockStart, true)
            coor.setEnd(thickEnd)
            this.drawRectangle(coor, colorRGB, yCoor,
              height, null, colorRGB,
              give.TrackDOMBehaviorImpl.VALIGN_CENTER
            )
            blockStart = thickEnd
          }
          isThick = (thickStart < blockEnd) && (thickEnd > blockStart)
        } else {
          isThick = true
        }
        coor.setStart(blockStart, true)
        coor.setEnd(blockEnd)
        this.drawRectangle(coor, colorRGB,
          yCoor, height * (isThick ? 1 : halfHeightRatio),
          this.mainSvg, colorRGB, give.TrackDOMBehaviorImpl.VALIGN_CENTER
        )
      }.bind(this)

      var coor = new give.ChromRegion({chr: transcript.chr,
        start: transcript.getStart(),
        end: transcript.getEnd(),
        strand: transcript.strand})
      var blockStart, blockEnd

      if (transcript.getNumOfBlocks && transcript.getNumOfBlocks()) {
        // Have different blocks
        blockEnd = transcript.getStart() + transcript.getBlockStarts()[0]
        // draw thick blocks and connecting lines
        for (let i = 0; i < transcript.getNumOfBlocks(); i++) {
          blockStart = transcript.getStart() +
            transcript.getBlockStarts()[i]
          // first draw connecting lines (intron, if any)
          if (blockEnd < blockStart) {
            coor.setStart(blockEnd, true)
            coor.setEnd(blockStart)
            this._drawSpanningLine(coor, colorRGB, yCoor, height)
          }
          blockEnd = blockStart + transcript.getBlockSizes()[i]
          _processSingleBlock(blockStart, blockEnd,
            transcript.thickStart, transcript.thickEnd, coor
          )
        }
      } else {
        // no blocks
        _processSingleBlock(transcript.getStart(), transcript.getEnd(),
          transcript.thickStart, transcript.thickEnd, coor
        )
      }
    },

    /**
     * _drawSingleTranscriptBodyRaster - draw a single transcript in the
     *    track SVG, rasterized by provided raster or `this._Raster`
     *
     * @param  {TranscriptObject} transcript - the transcript to be
     *    drawn. Only the entry provided here will be drawn, so if a gene
     *    is provided, only the gene, not its transcript(s) will be drawn.
     * @param  {boolean} [lastTrans] - whether this transcript is the last one
     *    to draw. Used to finish rasterized drawing.
     * @param  {number} yCoor - the y coordinate from the top to draw this
     *    transcript. __Notice that genes drawn are vertically-aligned
     *    to the center so yCoor should be the coordinates at the center.__
     * @param  {number} colorRGB - RGB value of the color for the gene
     * @param  {number} height - full height for drawing (CDS for
     *    example).
     * @param  {number} halfHeightRatio - Ratio for lower height values in
     *    some regions (for example, UTRs). __This should be normalized by
     *    `height`.__
     * @param  {number} lineHeight - unit for all height values.
     */
    _drawSingleTranscriptBodyRaster: function (transcript, lastTrans, yCoor,
      colorRGB, height, halfHeightRatio, lineHeight
    ) {
      /**
       * Steps:
       * 1. Find the raster index for the transcript;
       * 2. Fill RasterizedInfo for the current raster until coordinate
       *    shifts away. Old RasterizedInfo may be used if available;
       * 3. Use RasterizedInfo to assign RasterizationState, the actual
       *    value to draw for current raster;
       * 4. Draw bodies based on RasterizationState
       */

      var rasterFlags = []
      var addRasToFlags = (flag, index) => rasterFlags.push(flag)

      var finishRas = lastTrans ||
        (this.activeVisibility > give.TrackObject.StatusEnum.VIS_DENSE)

      /**
       * _processSingleBlock - helper function to draw only one block
       *
       * @param  {number} blockStart - the start coordinate of the block.
       * @param  {number} blockEnd - the end coordinate of the block.
       * @param  {number} [thickStart] - the start coordinate of thick
       *    portions, which may overlap with the block.
       * @param  {number} [thickEnd] - the end coordinate of thick
       *    portions, which may overlap with the block.
       */
      var _processSingleBlock = function (
        blockStart, blockEnd, thickStart, thickEnd
      ) {
        var isThick = false
        if (blockStart < this._RasInfo.getRasterStart()) {
          blockStart = this._RasInfo.getRasterStart()
        }
        while (blockStart < blockEnd) {
          var segEnd = Math.min(blockEnd, this._RasInfo.getRasterEnd())
          if (typeof thickStart === 'number' ||
            typeof thickEnd === 'number'
          ) {
            if (thickStart < segEnd && thickStart > blockStart) {
              // CDS start is in this segment
              this._RasInfo.addData(thickStart - blockStart,
                give.RasInfo.RASTER_STATES.THIN)
              blockStart = thickStart
            }
            if (thickEnd < segEnd && thickEnd > blockStart) {
              // CDS end is in this block
              this._RasInfo.addData(thickEnd - blockStart,
                give.RasInfo.RASTER_STATES.THICK)
              blockStart = thickEnd
            }
            isThick = (thickStart < segEnd) && (thickEnd > blockStart)
          } else {
            isThick = true
          }
          this._RasInfo.addData(segEnd - blockStart,
            isThick ? give.RasInfo.RASTER_STATES.THICK
              : give.RasInfo.RASTER_STATES.THIN)

          blockStart = segEnd
          if (blockStart >= this._RasInfo.getRasterEnd()) {
            // moved past the current raster, finish it and append to
            // rasterFlags
            this._RasInfo.next(addRasToFlags)
          }
        }
        return blockStart
      }.bind(this)

      var blockStart = transcript.getStart()
      var rasterDrawIndex

      try {
        if (this.activeVisibility > give.TrackObject.StatusEnum.VIS_DENSE) {
          rasterDrawIndex = this._RasInfo.findRasIndex(blockStart)
          this._RasInfo.prepareInfo(rasterDrawIndex)
        } else {
          rasterDrawIndex = this._RasInfo.getCurrIndex()
          let targetIndex = this._RasInfo.findRasIndex(blockStart)
          while (this._RasInfo.getCurrIndex() < targetIndex) {
            this._RasInfo.next(addRasToFlags)
          }
        }

        if (transcript.getNumOfBlocks && transcript.getNumOfBlocks()) {
          // Have different blocks
          let blockEnd = blockStart + transcript.getBlockStarts()[0]
          for (let i = 0; i < transcript.getNumOfBlocks(); i++) {
            blockStart = transcript.getStart() +
              transcript.getBlockStarts()[i]
            // first draw connecting lines (intron, if any)
            if (blockEnd < blockStart) {
              // fill rasters in the gap with `give.RasInfo.RASTER_STATES.LINE`
              blockEnd = Math.max(this._RasInfo.getRasterStart(), blockEnd)
              while (blockEnd < blockStart) {
                let segEnd = Math.min(blockStart, this._RasInfo.getRasterEnd())
                this._RasInfo.addData(segEnd - blockEnd,
                  give.RasInfo.RASTER_STATES.LINE)
                if (segEnd === this._RasInfo.getRasterEnd()) {
                  this._RasInfo.next(addRasToFlags)
                }
                blockEnd = segEnd
              }
            }
            blockEnd = blockStart + transcript.getBlockSizes()[i]
            _processSingleBlock(blockStart, blockEnd,
              transcript.thickStart, transcript.thickEnd
            )
          }
        } else {
          // no blocks
          _processSingleBlock(blockStart, transcript.getEnd(),
            transcript.thickStart, transcript.thickEnd)
        }
        if (finishRas) {
          this._RasInfo.next(addRasToFlags)
        }
      } catch (ignore) {
      } finally {
        // finish rasterFlags
        if (rasterFlags.length > 0) {
          let rasOutput = this._RasInfo.genRasOutput(
            rasterFlags, rasterDrawIndex)
          let coor
          rasOutput.forEach(function (rasObj, index) {
            if (rasObj.flag) {
              coor = coor || new give.ChromRegion({
                chr: transcript.chr,
                start: rasObj.start,
                end: rasObj.end,
                strand: transcript.strand
              })
              coor.setStart(rasObj.start, true)
              coor.setEnd(rasObj.end)
              if (rasObj.flag === give.RasInfo.RASTER_STATES.LINE) {
                // draw spanning line
                this._drawSpanningLine(coor, colorRGB, yCoor, height)
              } else {
                this.drawRectangle(coor, colorRGB,
                  yCoor, (rasObj.flag === give.RasInfo.RASTER_STATES.THICK
                    ? 1 : halfHeightRatio) * height, this.mainSvg,
                  null, give.TrackDOMBehaviorImpl.VALIGN_CENTER
                )
              }
            }
          }, this)
        }
      }
    },

    _drawSpanningLine: function (region, colorRGB, y, height) {
      height = height || 1
      y = y || 0

      var svgToDraw = this.mainSvg
      var windowToDraw = svgToDraw.viewWindow

      if (windowToDraw.overlaps(region) > 0) {
        var x0 = this.transformXCoordinate(region.getStartCoor(), true)
        var x1 = this.transformXCoordinate(region.getEndCoor(), true)
        this.drawLine(x0, y, x1, y, colorRGB)
        this.drawStrandArrows(x0, y - 0.5 * height, x1, y + 0.5 * height,
          region.getStrand(), colorRGB)
      }
    }
  })

  return give
})(GIVe || {})
  </script>
</dom-module>
